feat(adapter): render typed history frames - #52
Conversation
Greptile SummaryThis PR moves typed
Confidence Score: 3/5Safe to merge for the common HistoryFrame-based agentic path; the _format_outputs fallback for legacy dict entries with mixed known/unknown output fields generates a malformed assistant prompt turn. The new to_lm_messages path works correctly for the primary use case. The _format_outputs fallback branch calls format_assistant_message_content (which already ends with [[ ## completed ## ]]) and then appends a second marker, producing a structurally malformed assistant history turn for legacy dict entries with mixed fields — the exact case the PR promises to handle correctly. dspy/adapters/types/history.py — specifically the _format_outputs fallback path (lines 159–171) Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller
participant Adapter
participant History
Caller->>Adapter: format(signature, demos, inputs)
Adapter->>History: has_open_episode()
History-->>Adapter: true / false
Adapter->>History: to_lm_messages(adapter, signature_without_history)
loop each frame
History->>History: _entry_to_frame(signature, entry)
alt frame.inputs
History->>Adapter: format_user_message_content(inputs)
Adapter-->>History: user message
end
alt frame.outputs
History->>History: _format_outputs(adapter, signature, outputs)
History->>Adapter: format_assistant_message_content(signature_outputs)
Adapter-->>History: assistant content
History-->>History: assistant message
end
alt frame.observations
History->>History: _format_observations(observations)
History-->>History: user message (observations)
end
end
History-->>Adapter: list[LMMessage]
alt has_open_episode
Adapter->>Adapter: clear current inputs_copy
end
Adapter->>Adapter: "format_user_message_content({}, main_request=True)"
Note over Adapter: Respond with... appended as final user message
Adapter-->>Caller: [system, ...history turns..., user(Respond with...)]
Reviews (3): Last reviewed commit: "feat(adapter): render typed history fram..." | Re-trigger Greptile |
df0f0c3 to
e22f99f
Compare
ee12841 to
6cb45e0
Compare
e22f99f to
5d3fbcf
Compare
6cb45e0 to
34b573e
Compare
| sections = [] | ||
| if signature_outputs: | ||
| sections.append( | ||
| adapter.format_assistant_message_content( | ||
| signature, | ||
| signature_outputs, | ||
| missing_field_message="Not supplied for this conversation history message. ", | ||
| ).strip() | ||
| ) | ||
| for key, value in unknown_outputs.items(): | ||
| sections.append(f"[[ ## {key} ## ]]\n{self._format_observation_content(value)}") | ||
| sections.append("[[ ## completed ## ]]") | ||
| return "\n\n".join(section for section in sections if section) |
There was a problem hiding this comment.
Double
[[ ## completed ## ]] marker in mixed-output fallback
When outputs contains both signature-recognized fields (signature_outputs) and unrecognized fields (unknown_outputs), the fallback branch calls format_assistant_message_content — which itself appends [[ ## completed ## ]] — strips it, then appends the unknown-field sections and another [[ ## completed ## ]]. The resulting assistant history turn carries two end-of-turn sentinels, which can mislead the LM about the expected format or cause the DSPy response parser to stop early on any future response that contains a first completed marker before all fields.
The fast path (if signature_outputs and not unknown_outputs) is clean; the fix is to also strip the trailing [[ ## completed ## ]] from the format_assistant_message_content output before joining, so the final explicit sections.append("[[ ## completed ## ]]") is the only one.
Summary
Stack
Validation
uv run --extra dev pytest -q tests/adapters/test_history.py tests/adapters/test_history_formatting.py